/**
 * This plugin can enable a cell to cell drag and drop operation within the same
 * grid view.
 * 
 * Note that the plugin must be added to the grid view, not to the grid panel.
 * For example, using {@link Ext.panel.Table viewConfig}:
 * 
 * viewConfig: { plugins: { ptype: 'celldragdrop',
 *  // Remove text from source cell and replace with value of emptyText.
 * applyEmptyText: true,
 * 
 * //emptyText: Ext.String.htmlEncode('<<foo>>'),
 *  // Will only allow drops of the same type. enforceType: true } }
 */
Ext.define('Ext.ux.CellDragDrop', {
	extend : 'Ext.AbstractPlugin',
	alias : 'plugin.celldragdrop',

	uses : ['Ext.view.DragZone'],

	/**
	 * @cfg {Boolean} enforceType Set to `true` to only allow drops of the same
	 *      type.
	 * 
	 * Defaults to `false`.
	 */
	enforceType : false,

	/**
	 * @cfg {Boolean} applyEmptyText If `true`, then use the value of
	 *      {@link #emptyText} to replace the drag record's value after a node
	 *      drop. Note that, if dropped on a cell of a different type, it will
	 *      convert the default text according to its own conversion rules.
	 * 
	 * Defaults to `false`.
	 */
	applyEmptyText : false,

	/**
	 * @cfg {Boolean} emptyText If {@link #applyEmptyText} is `true`, then this
	 *      value as the drag record's value after a node drop.
	 * 
	 * Defaults to an empty string.
	 */
	emptyText : '',

	/**
	 * @cfg {Boolean} dropBackgroundColor The default background color for when
	 *      a drop is allowed.
	 * 
	 * Defaults to green.
	 */
	dropBackgroundColor : 'green',

	/**
	 * @cfg {Boolean} noDropBackgroundColor The default background color for
	 *      when a drop is not allowed.
	 * 
	 * Defaults to red.
	 */
	noDropBackgroundColor : 'red',

	// <locale>
	/**
	 * @cfg {String} dragText The text to show while dragging.
	 * 
	 * Two placeholders can be used in the text:
	 *  - `{0}` The number of selected items. - `{1}` 's' when more than 1 items
	 * (only useful for English).
	 */
	dragText : '{0} selected row{1}',
	// </locale>

	/**
	 * @cfg {String} ddGroup A named drag drop group to which this object
	 *      belongs. If a group is specified, then both the DragZones and
	 *      DropZone used by this plugin will only interact with other drag drop
	 *      objects in the same group.
	 */
	ddGroup : "GridDD",

	/**
	 * @cfg {Boolean} enableDrop Set to `false` to disallow the View from
	 *      accepting drop gestures.
	 */
	enableDrop : true,

	/**
	 * @cfg {Boolean} enableDrag Set to `false` to disallow dragging items from
	 *      the View.
	 */
	enableDrag : true,

	/**
	 * @cfg {Object/Boolean} containerScroll True to register this container
	 *      with the Scrollmanager for auto scrolling during drag operations. A
	 *      {@link Ext.dd.ScrollManager} configuration may also be passed.
	 */
	containerScroll : false,

	init : function(view) {
		var me = this;

		view.on('render', me.onViewRender, me, {
					single : true
				});
	},

	destroy : function() {
		var me = this;

		Ext.destroy(me.dragZone, me.dropZone);
	},

	enable : function() {
		var me = this;

		if (me.dragZone) {
			me.dragZone.unlock();
		}
		if (me.dropZone) {
			me.dropZone.unlock();
		}
		me.callParent();
	},

	disable : function() {
		var me = this;

		if (me.dragZone) {
			me.dragZone.lock();
		}
		if (me.dropZone) {
			me.dropZone.lock();
		}
		me.callParent();
	},

	onViewRender : function(view) {
		var me = this, scrollEl;

		if (me.enableDrag) {
			if (me.containerScroll) {
				scrollEl = view.getEl();
			}

			me.dragZone = new Ext.view.DragZone({
				view : view,
				ddGroup : me.dragGroup || me.ddGroup,
				dragText : me.dragText,
				containerScroll : me.containerScroll,
				scrollEl : scrollEl,
				getDragData : function(e) {
					var view = this.view, item = e.getTarget(view
							.getItemSelector()), record = view.getRecord(item), clickedEl = e
							.getTarget(view.getCellSelector()), dragEl;

					if (item) {
						dragEl = document.createElement('div');
						dragEl.className = 'x-form-text';
						dragEl.appendChild(document
								.createTextNode(clickedEl.textContent
										|| clickedEl.innerText));

						return {
							event : new Ext.EventObjectImpl(e),
							ddel : dragEl,
							item : e.target,
							columnName : view.getGridColumns()[clickedEl.cellIndex].dataIndex,
							record : record
						};
					}
				},

				onInitDrag : function(x, y) {
					var self = this, data = self.dragData, view = self.view, selectionModel = view
							.getSelectionModel(), record = data.record, el = data.ddel;

					// Update the selection to match what would have been
					// selected if the user had
					// done a full click on the target node rather than starting
					// a drag from it.
					if (!selectionModel.isSelected(record)) {
						selectionModel.select(record, true);
					}

					self.ddel.update(el.textContent || el.innerText);
					self.proxy.update(self.ddel.dom);
					self.onStartDrag(x, y);
					return true;
				}
			});
		}

		if (me.enableDrop) {
			me.dropZone = new Ext.dd.DropZone(view.el, {
				view : view,
				ddGroup : me.dropGroup || me.ddGroup,
				containerScroll : true,

				getTargetFromEvent : function(e) {
					var self = this, v = self.view, cell = e
							.getTarget(v.cellSelector), row, columnIndex;

					// Ascertain whether the mousemove is within a grid cell.
					if (cell) {
						row = v.findItemByChild(cell);
						columnIndex = cell.cellIndex;

						if (row && Ext.isDefined(columnIndex)) {
							return {
								node : cell,
								record : v.getRecord(row),
								columnName : self.view.up('grid').columns[columnIndex].dataIndex
							};
						}
					}
				},

				// On Node enter, see if it is valid for us to drop the field on
				// that type of column.
				onNodeEnter : function(target, dd, e, dragData) {
					var self = this, destType = target.record.fields
							.get(target.columnName).type.type.toUpperCase(), sourceType = dragData.record.fields
							.get(dragData.columnName).type.type.toUpperCase();

					delete self.dropOK;

					// Return if no target node or if over the same cell as the
					// source of the drag.
					if (!target || target.node === dragData.item.parentNode) {
						return;
					}

					// Check whether the data type of the column being dropped
					// on accepts the
					// dragged field type. If so, set dropOK flag, and highlight
					// the target node.
					if (me.enforceType && destType !== sourceType) {

						self.dropOK = false;

						if (me.noDropCls) {
							Ext.fly(target.node).addCls(me.noDropCls);
						} else {
							Ext.fly(target.node).applyStyles({
										backgroundColor : me.noDropBackgroundColor
									});
						}

						return;
					}

					self.dropOK = true;

					if (me.dropCls) {
						Ext.fly(target.node).addCls(me.dropCls);
					} else {
						Ext.fly(target.node).applyStyles({
									backgroundColor : me.dropBackgroundColor
								});
					}
				},

				// Return the class name to add to the drag proxy. This provides
				// a visual indication
				// of drop allowed or not allowed.
				onNodeOver : function(target, dd, e, dragData) {
					return this.dropOK ? this.dropAllowed : this.dropNotAllowed;
				},

				// Highlight the target node.
				onNodeOut : function(target, dd, e, dragData) {
					var cls = this.dropOK ? me.dropCls : me.noDropCls;

					if (cls) {
						Ext.fly(target.node).removeCls(cls);
					} else {
						Ext.fly(target.node).applyStyles({
									backgroundColor : ''
								});
					}
				},

				// Process the drop event if we have previously ascertained that
				// a drop is OK.
				onNodeDrop : function(target, dd, e, dragData) {
					if (this.dropOK) {
						target.record.set(target.columnName, dragData.record
										.get(dragData.columnName));
						if (me.applyEmptyText) {
							dragData.record.set(dragData.columnName,
									me.emptyText);
						}
						return true;
					}
				},

				onCellDrop : Ext.emptyFn
			});
		}
	}
});
